# REST framework之版本和解析器
# 版本(获取URL中传入的版本)
简单通过url携带的参数,在后端进行获取
#url.py文件中 from django.conf.urls import url from api import views urlpatterns = [ url(r'users/$',views.UsersView.as_view()) ] # views.py文件中 from django.shortcuts import render,HttpResponse from rest_framework.views import APIView class UsersView(APIView): def get(self,request, *args, **kwargs): #version= request._request.GET.get('version') # 通过_request获得参数 version= request.query_params.get('version') print(version) return HttpResponse('用户列表')
小提示:
1、通过rest_framework封装的reuqest函数,可以调用query_params属性获得原有request的参数 from rest_framework.request import Request ... @property def query_params(self): # 该函数用于返回原有的request的所有参数 return self._request.GET
# 1、简单自定义版本获取类(GET传参:URL后?version=v1)
from django.shortcuts import render,HttpResponse
from rest_framework.request import Request
from rest_framework.views import APIView
class ParamVersion:
def determine_version(self,request,*args,**kwargs):
version = request.query_params.get('version')
return version
class UsersView(APIView):
versioning_class = ParamVersion # 不再是一个列表了,是一个类
def get(self,request, *args, **kwargs):
print(request.version) # 这样就可以获取url中的version对应得值了, api/users/?version=1
return HttpResponse('用户列表')
# RESTframework自带得版本获取器
from rest_framework.views import APIView
from rest_framework.versioning import QueryParameterVersioning
class UsersView(APIView):
versioning_class = QueryParameterVersioning # 直接就可以用了
def get(self,request, *args, **kwargs):
print(request.version)
return HttpResponse('用户列表')
# 源码分析:
点击QueryParameterVersioning,查看原有代码,设计三个配置项,如下图 class BaseVersioning: default_version = api_settings.DEFAULT_VERSION allowed_versions = api_settings.ALLOWED_VERSIONS version_param = api_settings.VERSION_PARAM def determine_version(self, request, *args, **kwargs): msg = '{cls}.determine_version() must be implemented.' raise NotImplementedError(msg.format( cls=self.__class__.__name__ )) def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra): return _reverse(viewname, args, kwargs, request, format, **extra) def is_allowed_version(self, version): if not self.allowed_versions: return True return ((version is not None and version == self.default_version) or (version in self.allowed_versions)) class QueryParameterVersioning(BaseVersioning): invalid_version_message = _('Invalid version in query parameter.') def determine_version(self, request, *args, **kwargs): version = request.query_params.get(self.version_param, self.default_version) if not self.is_allowed_version(version): raise exceptions.NotFound(self.invalid_version_message) return version def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra): url = super().reverse( viewname, args, kwargs, request, format, **extra ) if request.version is not None: return replace_query_param(url, self.version_param, request.version) return url
小提示:
1、 self.version_param: #表示version得名称是什么? self.default_version: # 默认版本 self.is_allowed_version: #用户判断是否是被允许的版本号
# 在settings中配置相关参数
REST_FRAMEWORK = {
'DEFAULT_VERSION':'v1', #默认v1,不传的话
'ALLOWED_VERSIONS':['v1','v2'],
'VERSION_PARAM':'version' # 允许得key;version=v1
}
# 2、在URL中传参 (如:api/v1,局部)
通过在URLPathVersioning
类进行版本获取
# url.py文件中
urlpatterns = [
url(r'(?P<version>[v1|v2]+)/users/$',views.UsersView.as_view())
]
# 视图函数中
from rest_framework.views import APIView
from rest_framework.versioning import URLPathVersioning
class UsersView(APIView):
versioning_class = URLPathVersioning
def get(self,request, *args, **kwargs):
print(request.version)
return HttpResponse('用户列表')
小提示:
以上的方法对:8000/api/v1/users/ --> 将会取出v1,注意也是要集合配置了settings中的配置REST_FRAMEWORK使用
# 全局配置
1、只需要在settings中的配置项中添加,(在APIView中可以看见有个DEFAULT_VERSION_CLASS的选项)
REST_FRAMEWORK = {
'DEFAULT_VERSION':'v1',
'ALLOWED_VERSIONS':['v1','v2'],
'VERSION_PARAM':'version', # 允许得key;version=v1
'DEFAULT_VERSIONING__CLASS':'rest_framework.versioning.URLPathVersioning',
} # 这样视图函数中什么也不用写了,所有的视图函数都可获取version了
# 版本组件源码流程
1、依旧从dispatch进入,封装request后,执行initial函数,在走认证之前
def determine_version(self, request, *args, **kwargs):
if self.versioning_class is None:
return (None, None)
scheme = self.versioning_class() # versioning_class就是我们setting中配置的类的对象
return (scheme.determine_version(request, *args, **kwargs), scheme) #第一个返回版本
...
def initial(self, request, *args, **kwargs):
self.format_kwarg = self.get_format_suffix(**kwargs)
neg = self.perform_content_negotiation(request)
request.accepted_renderer, request.accepted_media_type = neg
version, scheme = self.determine_version(request, *args, **kwargs) #处理版本对象
request.version, request.ver sioning_scheme = version, scheme
self.perform_authentication(request) # 认证
self.check_permissions(request) # 权限
self.check_throttles(request) # 节流
小提示:
from rest_framework.views import APIView class UsersView(APIView): def get(self,request, *args, **kwargs): # 获取版本 print(request.version) # 获取处理版本的对象 print(request.versioning_scheme) #<rest_framework.versioni58676438> u1 = request.versioning_scheme.reverse(viewname='uuu',request=request) print(u1) # http://127.0.0.1:8000/api/v1/users/ return HttpResponse('用户列表')
# 版本总结
1、配置settings.py
REST_FRAMEWORK = {
'DEFAULT_VERSION':'v1',
'ALLOWED_VERSIONS':['v1','v2'],
'VERSION_PARAM':'version', # 允许得key;version=v1
'DEFAULT_VERSIONING__CLASS':'rest_framework.versioning.URLPathVersioning',
} # 这样视图函数中什么也不用写了,所有的视图函数都可获取version了
2、视图中,只要没有重写versioning_class,那么就可以通过全局获取version
# 解析器(依靠请求头的Content-Type对请求体中的数据解析到request.data中)
前戏:
django中:request.POST / request.body的区分? 我们可以来打印以下django中的request的类型,发现他是:django.core.handlers.wsgi.WSGIRequest的对象,我们点开WSGIRequeset来查看里面的方法,我们找到POST,点击_get_post方法-->再点击_load_post_and_files,在这个方法中有: if self.content_type == 'multipart/form-data':pass # 表示上传文件 elif self.content_type == 'application/x-www-form-urlencoded':pass # 如果content_type是这个头,那么我们的request.POST中才有数据,并且是个QueryDict(去request.body中解析) 1、要求请求头内容格式为:Content-Type:application/x-www-form-urlencoded 2、数据格式要求:name=alex&age=19&gender=男
如果上面两个要求都满足了,那么request.POST中才有值
如: a. form表单提交,默认的请求头内容格式为:Content-Type:application/x-www-form-urlencoded <form method ...> # 这里可以更改请求头内容格式 ...
b. ajax提交,将下面的data转换为name=alex&age=19&gender=男类型,并带有Content-Type:application/x-www-form-urlencoded传到后端
$.ajax({
url:...,
type:POST,
data:{name:alex,age:18}
}) # 默认这种传参会自动转换格式为name=alex&age=19&gender=男,所以POST中有数据
# ajax定制请求头内容格式
$.ajax({
url:...,
type:POST,
headers:{'Content-Type':'application/json'} #这种情况request.POST没有数据,body有
data:{name:alex,age:18}
})
#ajax更改请求头,同时更改了data数据
$.ajax({
url:...,
type:POST,
headers:{'Content-Type':'application/json'}, #这种情况request.POST没有数据,body有
data:JSON.stringfy({name:alex,age:18}), # 将字典转换为字符串了,格式也不是之前的格式
}) # body有值"{'name':'alex','age':'18'}",POST没有值
- **小提示:**
```
通过上面的知识我们知道了,django对提交的数据只支持满足两种条件才能取出值,(1,固定的请求头格式;2,固定的传参方式);所以我们需要一种解析器,用来解析数据。
也是就说上面我们发的json数据,不满足传参格式,必须在body中获取并转换才行,比较麻烦
```

#### REST framework解析器
##### 简单使用解析器
```python
from rest_framework.parsers import JSONParser,FormParser
class ParserView(APIView):
parser_classes = [JSONParser,FormParser,]
def post(self, request, *args, **kwargs):
'''
允许用户发送JSON格式数据
a.content-type:application/json
b.{'name':'alex','age':18}
JSONParser:表示只能解析content-type:application/json头的数据
FormParser:表示只能解析Content-Type:application/x-www-form-urlencoded头的数据
'''
print(request.data)
ret = {}
ret['name1'] = request.data['name']
return JsonResponse(ret)
# 在postman的请求体中发送json数据:
{
"name":"alex",
"age":18
}
# 返回:{"name1": "alex"}
总结:
如果不使用解析器,那么后端拿到的数据是不会出现在POST中的,且格式混乱。
# 解析器流程分析
1、进入Request类,找到data属性
@property
def data(self):
if not _hasattr(self, '_full_data'):
self._load_data_and_files() #此函数用于返回加载的数据
return self._full_data
2、进入_load_data_and_files函数后,执行self._parse() 函数,在此函数中
2.1、media_type = self.content_type # 获取用户请求头格式
2.2、parser = self.negotiator.select_parser(self,self.parsers) # parses是自己类中写的解析器列表,self是请求对象,包含了self.content_type,进行匹配,选择parser,即该选那个解析器
2.3、parsed = parser.parse(stream, media_type, self.parser_context) #执行各自的parse方法
3、假如此时进入的是JSONParser类中parser方法
def parse(self, stream, media_type=None, parser_context=None):
parser_context = parser_context or {}
encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET)
try:
decoded_stream = codecs.getreader(encoding)(stream) # 获取请求体
parse_constant = json.strict_constant if self.strict else None
return json.load(decoded_stream, parse_constant=parse_constant) # 加载数据
except ValueError as exc:
raise ParseError('JSON parse error - %s' % str(exc))
4、执行dispatch方法的是有,在封装Request的时候
def initialize_request():
...
return Request(
request,
parsers=self.get_parsers(), # 这里触发一个列表生成式,将每个解析器封装在request中
authenticators=self.get_authenticators(), #认证对象
negotiator=self.get_content_negotiator(),
parser_context=parser_context
)
# 全局配置解析器
通过上面的分析,我们也就可以在全局配置解析器器
REST_FRAMEWORK = {
'DEFAULT_VERSION':'v1',
'ALLOWED_VERSIONS':['v1','v2'],
'VERSION_PARAM':'version', # 允许得key;version=v1
'DEFAULT_VERSIONING_CLASS':'rest_framework.versioning.URLPathVersioning',
'DEFAULT_PARSER_CLASSES':['rest_framework.parsers.JSONParser', # 配置解析器
'rest_framework.parsers.FormParser'],
}
# 也就是说我们的视图函数中只需要request.data即可
class ParserView(APIView):
def post(self, request, *args, **kwargs):
print(request.data)
ret = {}
ret['name1'] = request.data['name']
return JsonResponse(ret)
# 总结:
1、直接在全局settings中配置DEFAULT_PARSER_CLASSES,和解析器即可
2、如果某视图项逃逸某个解析器,既可以重新选择自己的解析器
# 源码流程 & 本质
本质:
请求头:
1.1: Accept:浏览器可接受的MIME类型。
1.2: Accept-Charset:浏览器可接受的字符集。
1.3: Content-Length:表示请求消息正文的长度。
1.4: Cookie:这是最重要的请求头信息之一
1.5: Host:初始URL中的主机和端口。
1.6: User-Agent:浏览器类型,如果Servlet返回的内容与浏览器类型有关则该值非常有用。
1.7: Content-Type 表示后面的文档属于什么MIME类型
状态码:
2.2: 200 (成功) 服务器已成功处理了请求。 通常。
2.3: 304 (未修改) 自从上次请求后,请求的网页未修改过。 服务器返回此响应时,不会返回网页内容。
2.4: 400 (错误请求) 服务器不理解请求的语法。
2.5: 403 (禁止) 服务器拒绝请求。可能是Forbiend,可能是crsf
2.6: 404 (未找到) 服务器找不到请求的网页。
2.7: 500 (服务器内部错误) 服务器遇到错误,无法完成请求。
2.8: 503 (服务不可用) 服务器目前无法使用(由于超载或停机维护)。 通常,这只是暂时状态。
请求方法:
1、GET方法
GET方法用于使用给定的URI从给定服务器中检索信息,即从指定资源中请求数据。使用GET方法的请求应该只是检索数据,并且不应对数据产生其他影响。在GET请求的URL中发送查询字符串(名称/值对),需要这样写:
/test/demo_form.php?name1=value1&name2=value2
说明:
GET请求是可以缓存的,我们可以从浏览器历史记录中查找到GET请求,还可以把它收藏到书签中;且GET请求有长度限制,仅用于请求数据(不修改)。注:因GET请求的不安全性,在处理敏感数据时,绝不可以使用GET请求。
2、POST方法
POST方法用于将数据发送到服务器以创建或更新资源,它要求服务器确认请求中包含的内容作为由URI区分的Web资源的另一个下属。POST请求永远不会被缓存,且对数据长度没有限制;我们无法从浏览器历史记录中查找到POST请求。
3、HEAD方法
HEAD方法与GET方法相同,但没有响应体,仅传输状态行和标题部分。这对于恢复相应头部编写的元数据非常有用,而无需传输整个内容。
4、PUT方法
PUT方法用于将数据发送到服务器以创建或更新资源,它可以用上传的内容替换目标资源中的所有当前内容。
它会将包含的元素放在所提供的URI下,如果URI指示的是当前资源,则会被改变。如果URI未指示当前资源,则服务器可以使用该URI创建资源。
5、DELETE方法
DELETE方法用来删除指定的资源,它会删除URI给出的目标资源的所有当前内容。
6、CONNECT方法
CONNECT方法用来建立到给定URI标识的服务器的隧道;它通过简单的TCP / IP隧道更改请求连接,通常实使用解码的HTTP代理来进行SSL编码的通信(HTTPS)。
7、OPTIONS方法
OPTIONS方法用来描述了目标资源的通信选项,会返回服务器支持预定义URL的HTTP策略。
8、TRACE方法
TRACE方法用于沿着目标资源的路径执行消息环回测试;它回应收到的请求,以便客户可以看到中间服务器进行了哪些(假设任何)进度或增量。